查看原文
其他

腾讯新开源的插件化框架 Shadow,原来是这么玩的

ZY5A59 郭霖 2020-10-29


/   今日科技快讯   /


8月27日,哔哩哔哩发布了2019年第二季度财报。月活用户连续攀升和营收结构趋于平衡,成为本季度B站成绩单的关键词。根据财报显示,第二季度,B站月活用户达到1.1亿,实现了900万的环比净增,创造了B站自2017年以来单季增长的记录。在此基础上,移动端月活达9620万,同比增长35%;日活用户达3320万,同比增长41%。


/   作者简介   /


本篇文章来自ZY5A59的投稿,分享了他对Shadow插件化框架的相关理解,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


ZY5A59的博客地址:
https://juejin.im/user/58d9d015ac502e0058df1f96

/   框架简单介绍   /


Shadow是最近腾讯开源的一款插件化框架。原理是使用宿主代理的方式实现组件的生命周期。目前的插件化框架,大部分都是使用hook系统的方式来做的。使用代理的基本上没有成体系的框架,只是一些小demo,Shadow框架的开源,在系统api 控制越来越严格的趋势下,算是一个新的方向。Shadow最大的两个亮点是:

  1. 零反射

  2. 框架自身动态化


下面就具体分析一下框架的实现。

Shadow框架的开源地址:
https://github.com/Tencent/Shadow



/   框架结构分析   /


框架结构图



项目目录结构


├── projects
│   ├── sample // 示例代码
│   │   ├── README.md
│   │   ├── maven
│   │   ├── sample-constant // 定义一些常量
│   │   ├── sample-host // 宿主实现
│   │   ├── sample-manager // PluginManager 实现
│   │   └── sample-plugin // 插件的实现
│   ├── sdk // 框架实现代码
│   │   ├── coding // lint
│   │   ├── core
│   │   │   ├── common
│   │   │   ├── gradle-plugin // gradle 插件
│   │   │   ├── load-parameters
│   │   │   ├── loader // 负责加载插件
│   │   │   ├── manager // 装载插件,管理插件
│   │   │   ├── runtime // 插件运行时需要,包括占位 Activity,占位 Provider 等等
│   │   │   ├── transform // Transform 实现,用于替换插件 Activity 父类等等
│   │   │   └── transform-kit
│   │   └── dynamic // 插件自身动态化实现,包括一些接口的抽象


框架主要类说明

PluginContainerActivity

代理Activity。

ShadowActivity

插件Activity统一父类,在打包时通过Transform统一替换。

ComponentManager

管理插件和宿主代理的对应关系。

PluginManager

装载插件。

PluginLoader

管理插件Activity生命周期等等。

sample示例代码 AndroidManifest.xml分析

注册sample MainActivity

负责启动插件。

注册代理Activity

注册了三个代理Activity,分别是 PluginDefaultProxyActivity,PluginSingleInstance1ProxyActivity,PluginSingleTask1ProxyActivity。可以看到,这三个Activity都是继承自PluginContainerActivity,只是设置了不同的launchMode,这里就明显的看出来,PluginContainerActivity就是代理Activity。

注册代理Provider

PluginContainerContentProvider也是代理Provider。

Activity实现

关于插件Activity的实现,我们主要看两个地方:

替换插件Activity的父类

  • 宿主中如何启动插件Activity

  • 插件中如何启动插件Activity


替换插件Activity的父类

Shadow中有一个比较巧妙的地方,就是插件开发的时候,插件的Activity还是正常继承Activity,在打包的时候,会通过Transform替换其父类为ShadowActivity。
projects/sdk/core/transform 和 projects/sdk/core/transform-kit 两个项目就是Transform,入口是ShadowTransform。这里对Transform做了一些封装,提供了友好的开发方式,这里就不多做分析了,我们主要看下TransformManager。


class TransformManager(ctClassInputMap: Map<CtClass, InputClass>,
                       classPool: ClassPool,
                       useHostContext: () -> Array<String>
) : AbstractTransformManager(ctClassInputMap, classPool) 
{

    override val mTransformList: List<SpecificTransform> = listOf(
            ApplicationTransform(),
            ActivityTransform(),
            ServiceTransform(),
            InstrumentationTransform(),
            RemoteViewTransform(),
            FragmentTransform(ctClassInputMap),
            DialogTransform(),
            WebViewTransform(),
            ContentProviderTransform(),
            PackageManagerTransform(),
            KeepHostContextTransform(useHostContext())
    )
}


这里的mTransformList就是要依次执行的Transform内容,也就是需要替换的类映射。我们以ApplicationTransform和ActivityTransform为例。


class ApplicationTransform : SimpleRenameTransform(
        mapOf(
                "android.app.Application"
                        to "com.tencent.shadow.core.runtime.ShadowApplication"
                ,
                "android.app.Application\$ActivityLifecycleCallbacks"
                        to "com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks"
        )
)

class ActivityTransform : SimpleRenameTransform(
        mapOf(
                "android.app.Activity"
                        to "com.tencent.shadow.core.runtime.ShadowActivity"
        )
)


可以看到,打包过程中,插件的Application会被替换成ShadowApplication,Activity会被替换成ShadowActivity,这里主要看一下ShadowActivity的继承关系。


为何插件Activity可以不用继承Activity呢?因为在代理Activity的方式中,插件Activity是被当作一个普通类来使用的,只要负责执行对应的生命周期即可。

宿主中如何启动插件Activity

宿主中启动插件Activity原理如下图:



我们就从sample里的MainActivity开始看起。

sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity是demo的主入口。启动插件的方式是:


startPluginButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // ...
        Intent intent = new Intent(MainActivity.this, PluginLoadActivity.class);
        intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);
        intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, "com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity");
        // ...
        startActivity(intent);
    }
});


可以看到,这里是通过PluginLoadActivity来启动的,传入了要启动的插件Activity:SplashActivity,接着就到PluginLoadActivity里看一下具体的启动。


class PluginLoadActivity extends Activity {
    public void startPlugin() {
        PluginHelper.getInstance().singlePool.execute(new Runnable() {
            @Override
            public void run() {
                HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);
                // ...
                bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));
                HostApplication.getApp().getPluginManager()
                        .enter(PluginLoadActivity.this, Constant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() {
                            @Override
                            public void onShowLoadingView(final View view) {
                                // 设置加载的样式
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        mViewGroup.addView(view);
                                    }
                                });
                            }
                            // ...
                        });
            }
        });
    }
}


这里可以看到,是通过HostApplication获取到PluginManager,然后调用其enter方法,进入插件。这里先看看返回的PluginManager是什么。


class HostApplication extends Application {
    public void loadPluginManager(File apk) {
        if (mPluginManager == null) {
            // 创建 PluginManager
            mPluginManager = Shadow.getPluginManager(apk);
        }
    }

    public PluginManager getPluginManager() {
        return mPluginManager;
    }
}

public class Shadow {
    public static PluginManager getPluginManager(File apk){
        final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk);
        File tempPm = fixedPathPmUpdater.getLatest();
        if (tempPm != null) {
            // 创建 DynamicPluginManager
            return new DynamicPluginManager(fixedPathPmUpdater);
        }
        return null;
    }
}


可以看到,HostApplication里返回的其实是一个DynamicPluginManager实例,那么接下来就要看DynamicPluginManager的enter方法。


class DynamicPluginManager implements PluginManager {
    @Override
    public void enter(Context context, long fromId, Bundle bundle, EnterCallback callback) {
        // 加载 mManagerImpl 实现,这里涉及到了框架的自身动态化,在后面会讲到,这里只要知道,mManagerImpl 最终是 SamplePluginManager 实例即可
        updateManagerImpl(context);
        // mManagerImpl 是 SamplePluginManager 实例,调用其实现
        mManagerImpl.enter(context, fromId, bundle, callback);
        mUpdater.update();
    }
}


通过上面的代码我们知道了,调用DynamicPluginManager.enter会转发到SamplePluginManager.enter中去,接着就看看这个实现。


class SamplePluginManager extends FastPluginManager {
    public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {
        // ...
        // 启动 Activity
        onStartActivity(context, bundle, callback);
        // ...
    }

    private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) {
        // ...
        final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);
        // ...
        final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);
        if (callback != null) {
            // 创建 loading view
            final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);
            callback.onShowLoadingView(view);
        }
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // ...
                // 加载插件
                InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);
                // 创建插件 Intent
                Intent pluginIntent = new Intent();
                pluginIntent.setClassName(
                        context.getPackageName(),
                        className
                );
                if (extras != null) {
                    pluginIntent.replaceExtras(extras);
                }
                // 启动插件 Activity
                startPluginActivity(context, installedPlugin, partKey, pluginIntent);
                // ...
            }
        });
    }
}


在SamplePluginManager.enter中,调用onStartActivity启动插件Activity,其中开线程去加载插件,然后调用 startPluginActivity。startPluginActivity 实现在其父类FastPluginManager里。


class FastPluginManager {
    public void startPluginActivity(Context context, InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
        Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent);
        if (!(context instanceof Activity)) {
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
    }
}


其中的重点是convertActivityIntent,将插件intent转化成宿主的intent,然后调用 系统的context.startActivity启动插件。这里的context是PluginLoadActivity.this,从其enter方法中一直传进来的。下面重点看看convertActivityIntent的实现。


class FastPluginManager {
    public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
        // 创建 mPluginLoader
        loadPlugin(installedPlugin.UUID, partKey);
        // 先调用 Application onCreate 方法
        mPluginLoader.callApplicationOnCreate(partKey);
        // 转化插件 intent 为 代理 Activity intent
        return mPluginLoader.convertActivityIntent(pluginIntent);
    }
}


到了这里其实有一些复杂了,因为mPluginLoader是通过Binder去调用相关方法的。由于这里涉及到了Binder的使用,需要读者了解Binder相关的知识,代码比较繁琐,这里就不具体分析代码实现了,用一张图理顺一下对应的关系:



通过上面的Binder对应图,我们可以简单的理解为,调用mPluginLoader中的方法,就是调用DynamicPluginLoader中的方法,调用mPpsController的方法,就是调用PluginProcessService中的方法。所以这里的mPluginLoader.convertActivityIntent相当于调用了DynamicPluginLoader.convertActivityIntent。


internal class DynamicPluginLoader(hostContext: Context, uuid: String) {
    fun convertActivityIntent(pluginActivityIntent: Intent): Intent? {
        return mPluginLoader.mComponentManager.convertPluginActivityIntent(pluginActivityIntent)
    }
}


调用到了ComponentManager.convertPluginActivityIntent方法。


abstract class ComponentManager : PluginComponentLauncher {
    override fun convertPluginActivityIntent(pluginIntent: Intent): Intent {
        return if (pluginIntent.isPluginComponent()) {
            pluginIntent.toActivityContainerIntent()
        } else {
            pluginIntent
        }
    }

    private fun Intent.toActivityContainerIntent(): Intent {
        // ...
        return toContainerIntent(bundleForPluginLoader)
    }

    private fun Intent.toContainerIntent(bundleForPluginLoader: Bundle): Intent {
        val className = component.className!!
        val packageName = packageNameMap[className]!!
        component = ComponentName(packageName, className)
        val containerComponent = componentMap[component]!!
        val businessName = pluginInfoMap[component]!!.businessName
        val partKey = pluginInfoMap[component]!!.partKey

        val pluginExtras: Bundle? = extras
        replaceExtras(null as Bundle?)

        val containerIntent = Intent(this)
        containerIntent.component = containerComponent

        bundleForPluginLoader.putString(CM_CLASS_NAME_KEY, className)
        bundleForPluginLoader.putString(CM_PACKAGE_NAME_KEY, packageName)

        containerIntent.putExtra(CM_EXTRAS_BUNDLE_KEY, pluginExtras)
        containerIntent.putExtra(CM_BUSINESS_NAME_KEY, businessName)
        containerIntent.putExtra(CM_PART_KEY, partKey)
        containerIntent.putExtra(CM_LOADER_BUNDLE_KEY, bundleForPluginLoader)
        containerIntent.putExtra(LOADER_VERSION_KEY, BuildConfig.VERSION_NAME)
        containerIntent.putExtra(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid)
        return containerIntent
    }
}


这里最终调用到toContainerIntent方法,终于水落石出了。在toContainerIntent中,创建了新的宿主代理Activity的 intent,这里的containerComponent对应的就是前面在Manifest里注册的PluginDefaultProxyActivity,返回代理activity intent 以后,调用context.startActivity(intent)就启动了代理Activity。

PluginDefaultProxyActivity继承自PluginContainerActivity,这个也就是整个框架的代理Activity,在PluginContainerActivity里,就是常规的分发生命周期了。和之前在插件化原理里介绍的差不多了。中间通过HostActivityDelegate分发生命周期。


class ShadowActivityDelegate(private val mDI: DI) : HostActivityDelegate, ShadowDelegate() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        // 设置 application,resources 等等
        mDI.inject(this, partKey)
        // 创建插件资源
        mMixResources = MixResources(mHostActivityDelegator.superGetResources(), mPluginResources)
        // 设置插件主题
        mHostActivityDelegator.setTheme(pluginActivityInfo.themeResource)
        try {
            val aClass = mPluginClassLoader.loadClass(pluginActivityClassName)
            // 创建插件 activity
            val pluginActivity = PluginActivity::class.java.cast(aClass.newInstance())
            // 初始化插件 activity
            initPluginActivity(pluginActivity)
            mPluginActivity = pluginActivity
            //设置插件AndroidManifest.xml 中注册的WindowSoftInputMode
            mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.activityInfo.softInputMode)
            // 获取 savedInstanceState
            val pluginSavedInstanceState: Bundle? = savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)
            pluginSavedInstanceState?.classLoader = mPluginClassLoader
            // 调用插件 activity onCreate 
            pluginActivity.onCreate(pluginSavedInstanceState)
            mPluginActivityCreated = true
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }

    // 获取插件资源
    override fun getResources(): Resources {
        if (mDependenciesInjected) {
            return mMixResources;
        } else {
            return Resources.getSystem()
        }
    }
}


上面就是在宿主中启动插件Activity的整个流程,下面看看在插件中如何启动Activity的。

插件中如何启动插件Activity

插件中启动Activity原理如下图:


我们上面说到过,插件Activity会在打包过程中替换其父类为ShadowActivity,很明显了,在插件中启动Activity即调用startActivity,自然就是调用ShadowActivity的startActivity了。startActivity在其父类ShadowContext里实现,我们来具体看下。


class ShadowContext extends SubDirContextThemeWrapper {
    public void startActivity(Intent intent) {
        final Intent pluginIntent = new Intent(intent);
        // ...
        final boolean success = mPluginComponentLauncher.startActivity(this, pluginIntent);
        // ...
    }
}


可以看到,是通过mPluginComponentLauncher.startActivity继续调用的,mPluginComponentLauncher就是ComponentManager的一个实例,是在前面说到的初始化插件Activity的时候设置的。内部实现就比较简单了。


abstract class ComponentManager : PluginComponentLauncher {
    override fun startActivity(shadowContext: ShadowContext, pluginIntent: Intent): Boolean {
        return if (pluginIntent.isPluginComponent()) {
            shadowContext.superStartActivity(pluginIntent.toActivityContainerIntent())
            true
        } else {
            false
        }
    }
}

public class ShadowContext extends SubDirContextThemeWrapper {
    public void superStartActivity(Intent intent) {
        // 调用系统 startActivity
        super.startActivity(intent);
    }
}


通过调用toActivityContainerIntent转化intent为代理Activity的intent,然后调用系统startActivity启动代理Activity,剩下的步骤就和上面宿主启动插件Activity中讲到的一样了。到现在,我们就对框架中Activity的启动基本了解了。

/   Service实现   /


Service的实现,我们直接看 插件中如何启动的即可。看一下ShadowContext中的startService实现:


public class ShadowContext extends SubDirContextThemeWrapper {
    public ComponentName startService(Intent service) {
        if (service.getComponent() == null) {
            return super.startService(service);
        }
        Pair<Boolean, ComponentName> ret = mPluginComponentLauncher.startService(this, service);
        if (!ret.first)
            return super.startService(service);
        return ret.second;
    }
}


也是调用mPluginComponentLauncher.startService,这里我们就比较熟悉了,就是ComponentManager.startService。


abstract class ComponentManager : PluginComponentLauncher {
    override fun startService(context: ShadowContext, service: Intent): Pair<Boolean, ComponentName> {
        if (service.isPluginComponent()) {
            // 插件service intent不需要转换成container service intent,直接使用intent
            val component = mPluginServiceManager!!.startPluginService(service)
            // ...
        }
        return Pair(false, service.component)
    }
}


这里直接调用PluginServiceManager.startPluginService。

class PluginServiceManager(private val mPluginLoader: ShadowPluginLoader, private val mHostContext: Context) {
    fun startPluginService(intent: Intent): ComponentName? {
        val componentName = intent.component
        // 检查所请求的service是否已经存在
        if (!mAliveServicesMap.containsKey(componentName)) {
            // 创建 Service 实例并调用 onCreate 方法
            val service = createServiceAndCallOnCreate(intent)
            mAliveServicesMap[componentName] = service
            // 通过startService启动集合
            mServiceStartByStartServiceSet.add(componentName)
        }
        mAliveServicesMap[componentName]?.onStartCommand(intent, 0, getNewStartId())
        return componentName
    }

    private fun createServiceAndCallOnCreate(intent: Intent): ShadowService {
        val service = newServiceInstance(intent.component)
        service.onCreate()
        return service
    }
}


可以看到,在Shadow中对Service的处理很简单,直接调用其生命周期方法,不过如此的实现方式,可能会带来一些时序问题。


/   BroadcastReceiver实现   /


广播的实现也比较常规,在插件中动态注册和发送广播,直接调用系统的方法即可,因为广播不涉及生命周期等复杂的内容。需要处理的就是在Manifest中静态注册的广播。这个理论上也和我们之前讲解插件化原理时候实现基本一致,解析Manifest,然后进行动态注册。不过在Shadow的demo里,并没有做解析,就是直接写在了代码里。

// AndroidManifest.xml
        <receiver android:name="com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver">
            <intent-filter>
                <action android:name="com.tencent.test.action" />
            </intent-filter>
        </receiver>

// SampleComponentManager
public class SampleComponentManager extends ComponentManager {
    public List<BroadcastInfo> getBroadcastInfoList(String partKey) {
        List<ComponentManager.BroadcastInfo> broadcastInfos = new ArrayList<>();
        if (partKey.equals(Constant.PART_KEY_PLUGIN_MAIN_APP)) {
            broadcastInfos.add(
                    new ComponentManager.BroadcastInfo(
                            "com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver",
                            new String[]{"com.tencent.test.action"}
                    )
            );
        }
        return broadcastInfos;
    }
}


/   ContentProvider 实现   /


关于ContentProvider的实现,其实和之前插件化原理文章中思路是一致的,也是通过注册代理ContentProvider然后分发给插件Provider,这里就不多做介绍了。

/   框架自身动态化   /


Shadow框架还有一个特点,就是框架本身也实现了动态化,这里的实现主要是三步:

  1. 抽象接口类

  2. 在插件中实现工厂类

  3. 通过工厂类动态创建接口的实现


我们以PluginLoaderImpl为例来看看,在前面介绍Activity启动流程的时候,有说到mPluginLoader.convertActivityIntent用来转换插件intent为代理Activity的 intent,这里的mPluginLoader就是动态创建的。我们来看一下创建过程。创建入口在PluginProcessService.loadPluginLoader里。


public class PluginProcessService extends Service {
    void loadPluginLoader(String uuid) throws FailedException {
        // ...
        PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());
        // ...
    }
}


接下来需要看一下LoaderImplLoader的具体实现。


final class LoaderImplLoader extends ImplLoader {
    // 创建 PluginLoaderImpl 的工厂类
    private final static String sLoaderFactoryImplClassName
            = "com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl";

    // 动态创建 PluginLoaderImpl
    PluginLoaderImpl load(InstalledApk installedApk, String uuid, Context appContext) throws Exception {
        // 创建插件 ClassLoader
        ApkClassLoader pluginLoaderClassLoader = new ApkClassLoader(
                installedApk,
                LoaderImplLoader.class.getClassLoader(),
                loadWhiteList(installedApk),
                1
        );
        // 获取插件中的 工厂类
        LoaderFactory loaderFactory = pluginLoaderClassLoader.getInterface(
                LoaderFactory.class,
                sLoaderFactoryImplClassName
        );
        // 调用工厂类方法创建 PluginLoaderImpl 实例
        return loaderFactory.buildLoader(uuid, appContext);
    }
}


从上面的代码和注释来看,其实很简单,创建插件的ClassLoader,通过ClassLoader创建一个工厂类的实例,然后调用工厂类方法生成 PluginLoaderImpl。而工厂类和PluginLoaderImpl的实现都在插件中,就达到了框架自身的动态化。PluginManagerImpl也是一样的道理,在DynamicPluginManager.updateManagerImpl中通过ManagerImplLoader.load加载。

/   总结   /



其实整个框架看下来,没有什么黑科技,就是代理Activity的原理加上设计模式的运用。其实目前几大插件化框架,基本上都是hook系统为主,像使用代理Activity原理的,Shadow应该算是第一个各方面实现都比较完整的框架,带来的好处就是不用去调用系统限制的api,更加稳定。在系统控制越来越严格的趋势下,也算是一个比较好的选择。原理简单,其中的设计思想可以学习~


推荐阅读:
总是听到有人说AndroidX,到底什么是AndroidX?
看一看Facebook工程师是怎么评价《第一行代码》的
给你的Android应用穿件花衣服吧!


欢迎关注我的公众号
学习技术或投稿



长按上图,识别图中二维码即可关注

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存